import re
from urllib.parse import urlparse
import platform
import subprocess
import socket

PATTERN_2_WIDTH = re.compile(
    r'[\u4e00-\u9fa5]'                      # 基本汉字
    # r'|[\u3400-\u4dbf]'                     # 扩展 A
    # r'|[\u20000-\u2a6df]'                   # 扩展 B
    # r'|[\u2a700-\u2b73f]'                   # 扩展 C
    # r'|[\u2b740-\u2b81f]'                   # 扩展 D
    # r'|[\u2b820-\u2ceaf]'                   # 扩展 E
    # r'|[\u2ceb0-\u2ebef]'                   # 扩展 F
    r'|[\u3000-\u303f]'                     # 中文标点
    r'|[\uFF00-\uFFEF]'                     # 全角符号
)

def is_valid_ip(ip):
    """正则验证IP地址格式是否有效"""
    if re.match(r'^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$', ip):
        return True
    else:
        return False

def is_valid_domain(domain):
        """验证域名格式是否有效"""
        # 简单的域名验证正则表达式
        pattern = r'^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$'
        return re.match(pattern, domain) is not None


def is_valid_url(url: str) -> bool:
    """
    验证 URL 格式是否有效（支持协议、域名/IPv4/IPv6、端口、路径、查询参数）
    协议：http/https
    地址：域名、IPv4、IPv6
    端口：可选，1-65535
    路径：可选
    查询参数：可选
    """
    # 1. 正则：协议 + 主机 + 端口 + 路径 + 查询
    pattern = re.compile(
        r'^(https?)://'  # 协议
        r'('
        r'([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}'  # 域名
        r'|'
        r'(\d{1,3}\.){3}\d{1,3}'  # IPv4
        r'|'
        r'\[([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\]'  # IPv6
        r')'
        r'(:\d{1,5})?'  # 端口（可选）
        r'(/[^?\s]*)?'  # 路径（可选）
        r'(\?[^#\s]*)?'  # 查询参数（可选）
        r'$'
    )
    # 2. 正则匹配
    if not pattern.match(url):
        return False
    # 3. 端口范围检查
    parsed = urlparse(url)
    if parsed.port and not (1 <= parsed.port <= 65535):
        return False
    return True



def show_table(title, json_head, json_data):
    """
    打印 JSON 格式的表格数据
    :param title: 表格标题
    :param json_head: JSON 格式的表头，例如 [{'name': '姓名', 'key': 'name', 'width': 10, 'align': 'left'}, {'name': '年龄', 'key': 'age', 'width': 5}]
    :param json_data: JSON 格式的数据，例如 [{'name': '张三', 'age': 18}, {'name': '李四', 'age': 19}]
    """
    print("")
    data_count = len(json_data)
    if data_count == 0:
        print(title + ':')
        print("  暂无数据")
        return

    print(f"{title} (共{data_count}条数据):")
    # 打印分隔线
    print("==".join(["=" * h['width'] for h in json_head]))
    # 打印表头

    print("  ".join([f"{h['name']:<{calculate_width(h['name'], h['width'])}}" if h.get('align', 'left') == 'left' else f"{h['name']:^{calculate_width(h['name'], h['width'])}}" for h in json_head]))
    # 打印分隔线
    print("--".join(["-" * h['width'] for h in json_head]))
    # 打印数据行
    for row in json_data:
        # 检验数据是否符合表头，且为None的话显示为 -
        for h in json_head:
            if h['key'] not in row:
                row[h['key']] = ''
            elif row[h['key']] is None:
                row[h['key']] = '-'
            # 如果有字典映射，根据key获取value
            if h.get('dict', None) is not None:
                row[h['key']] = h['dict'].get(row[h['key']], '-')
            if h.get('render', None) is not None:
                row[h['key']] = h['render'](row)
        print("  ".join([f"{str(row.get(h['key'], '')):<{calculate_width(str(row.get(h['key'], '')), h['width'])}}" if h.get('align', 'left') == 'left' else f"{str(row.get(h['key'], '')):^{calculate_width(str(row.get(h['key'], '')), h['width'])}}" for h in json_head]))
    # 打印分隔线
    print("==".join(["=" * h['width'] for h in json_head]))
    # 最后再打印数量
    print(f"{title} (共{data_count}条数据)")

# 计算字符串中中文字符数量，如果中文字符多，则长度要适当变小，以便表格对齐
def calculate_width(s, width):
    """
    计算字符串的宽度，中文字符算2个宽度，其他字符算1个宽度
    :param s: 输入字符串
    :param width: 原始宽度
    :return: 调整后的宽度
    """
    # 中文字符的正则表达式，以及中文的字符例如（），。：等等
    pattern = re.compile(PATTERN_2_WIDTH)
    #pattern = re.compile(r'[\u4e00-\u9fa5]')
    # 统计中文字符数量
    chinese_count = len(re.findall(pattern, s))
    # 中文字符算2个宽度，其他字符算1个宽度
    return max(0, width - chinese_count)

def show_entity(title, json_head, json_data):
    """
    打印 JSON 格式的实体数据
    :param title: 实体标题
    :param json_head: JSON 格式的表头，例如 [{'name': '姓名', 'key': 'name'}, {'name': '年龄', 'key': 'age'}]
    :param json_data: JSON 格式的实体数据，例如 {'name': '张三', 'age': 18}
    """
    print("")
    if title and len(title) > 0:
        print(title + ':')
    for h in json_head:
        # 判断数据是否为空
        if h['key'] not in json_data or json_data[h['key']] is None:
            json_data[h['key']] = '-'
        # 如果有字典映射，根据key获取value
        if h.get('dict', None) is not None:
            json_data[h['key']] = h['dict'].get(json_data[h['key']], '-')
        print(f"  {h['name']}: {json_data.get(h['key'], '-')}")

def is_ip_online(ip, timeout=2):
    """检查目标IP是否在线"""
    if platform.system().lower() == 'windows':
        # Windows系统参数：发送2个包，超时4000ms
        cmd = ['ping', '-n', '2', '-w', str(timeout)+'000', ip]
    else:
        # Linux/Mac系统参数：发送2个包，超时4秒
        cmd = ['ping', '-c', '2', '-W', str(timeout), ip]
        
    # 执行ping命令并捕获输出
    result = subprocess.run(cmd, 
                            stdout=subprocess.PIPE, 
                            stderr=subprocess.PIPE,
                            timeout=timeout*2+2,  # 设置更长的全局超时时间
                            text=True)  # 使用文本模式方便解析输出
    
    # 不仅检查返回码，还检查输出中是否包含TTL信息来确认连通性
    success = result.returncode == 0 or ('TTL=' in result.stdout or 'ttl=' in result.stdout)
    return success
        
def check_tcp_port(ip, port, timeout=2):
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(timeout)
        result = sock.connect_ex((ip, port))
        sock.close()
        if result == 0:
            return True
    except Exception as e:
        pass
    return False

def check_udp_port(ip, port, timeout=2):
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.settimeout(timeout)
        
        # 发送测试数据
        test_data = b"1"
        sock.sendto(test_data, (ip, port))
        # 尝试接收响应
        data, addr = sock.recvfrom(1024)
        # 如果收到响应，说明端口开放
        return True
    except socket.timeout:
        # 注意：UDP超时并不一定意味着端口关闭，可能是没有响应
        # 这里我们保守处理，不将超时的UDP端口标记为开放
        pass
    except Exception as e:
        pass
    finally:
        sock.close()
    return False
      
def get_service_name(port, protocol):
    """获取端口对应的服务名称
    
    Args:
        port: 端口号
        protocol: 协议类型
    
    Returns:
        str: 服务名称
    """
    try:
        return socket.getservbyport(port, protocol)
    except (OSError, OverflowError):
        return f"unknown-{protocol}"


# ---------------- 测试 ----------------
if __name__ == '__main__':
    print(is_ip_online('8.8.8.8'))